package com.thunder.ktv.scoreengine2test.audio;

import android.media.AudioFormat;
import android.util.Log;

import com.thunder.ktv.scoreengine2sdk.ScoreEngine2;
import com.thunder.lib_audio.record.AudioRecordManager;
import com.thunder.lib_audio.record.IRecordDataCallback;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 录音数据提供者（独立文件）
 *
 * 使用 BlockingQueue 实现了一个生产者-消费者模式的音频数据缓冲池。
 * 生产者: onRecordData 回调，负责将录音数据(byte[])转换为short并放入队列。
 * 消费者: read 方法，负责从队列中取出short数据填充到目标buffer。
 */
public class TSAudioRecordProvider implements IAudioProvider {

    private static final String TAG = "TSAudioRecordProvider";

    // 使用BlockingQueue作为线程安全的缓冲区来存储short类型的音频样本
    // LinkedBlockingQueue 是一个无界队列，可以根据需要增长
    private final BlockingQueue<Short> audioBuffer = new LinkedBlockingQueue<>();

    // volatile 关键字确保多线程间的可见性
    private volatile boolean isRecording = false;

    // 音频数据回调接口
    // 这是“生产者”
    private final IRecordDataCallback callback = new IRecordDataCallback() {
        @Override
        public void onRecordData(byte[] bytes, int offset, int size) {
            // 如果没有在录音，则直接忽略收到的数据
            if (!isRecording) {
                return;
            }

            // PCM 16-bit 音频，每个样本(short)占2个字节。
            // 确保传入的数据长度是偶数
            if (size % 2 != 0) {
                Log.w(TAG, "Received odd-sized audio data, ignoring last byte. Size: " + size);
                size--; // 忽略最后一个字节
            }

            // 将 byte[] 转换为 short[] 并放入缓冲区
            // Android 默认使用小端序 (LITTLE_ENDIAN)
            for (int i = offset; i < offset + size; i += 2) {
                // 将两个 byte 合并成一个 short
                // (byte2 << 8) | (byte1 & 0xFF)
                // (bytes[i+1] << 8) 创建高8位
                // (bytes[i] & 0xFF) 创建低8位，& 0xFF 是为了防止 byte 自动符号扩展
                short sample = (short) ((bytes[i + 1] << 8) | (bytes[i] & 0xFF));
                try {
                    // offer 方法不会阻塞，如果队列实现有容量限制且满了，会返回 false
                    // 对于 LinkedBlockingQueue (无界)，这几乎总能成功
                    audioBuffer.put(sample);
                } catch (InterruptedException e) {
                    // 在添加数据时线程被中断，通常意味着要停止工作了
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    Log.e(TAG, "Thread interrupted while putting audio data into buffer.", e);
                }
            }
        }
    };

    @Override
    public void start() {
        Log.d(TAG, "start recording...");
        // 清空之前可能存在的旧数据
        audioBuffer.clear();
        isRecording = true;
        AudioRecordManager.getInstance()
                .registerRecordData(TAG, ScoreEngine2.SAMPLE_RATE,
                        AudioFormat.ENCODING_PCM_16BIT,
                        AudioFormat.CHANNEL_IN_LEFT, callback);
    }

    @Override
    public void pause() {
        Log.d(TAG, "pause recording...");
        // 一个简单的实现是直接停止接收数据
        isRecording = false;
    }

    @Override
    public void resume() {
        Log.d(TAG, "resume recording...");
        // 恢复接收数据
        isRecording = true;
    }

    @Override
    public void stop() {
        Log.d(TAG, "stop recording...");
        isRecording = false;
        AudioRecordManager.getInstance().unRegisterRecordData(TAG);
        // 清空缓冲区，释放内存，并唤醒可能在read()中等待的线程
        // 虽然设置 isRecording=false 最终会让 read 退出，但清空可以更快释放资源
        // 并且如果read在take()上阻塞，它不会立即响应 isRecording 的变化。
        // 一个优雅的做法是往队列里放一个特殊的“毒丸”值来唤醒它，
        // 但对于音频数据流，简单地清空并让poll超时是更简单的策略。
        audioBuffer.clear();
    }

    @Override
    public void release() {
        Log.d(TAG, "release resources...");
        stop();
    }

    /**
     * 读取指定大小的音频数据到 buffer 中
     * 这是“消费者”
     *
     * @param buffer 用于接收音频数据的 short 数组
     * @return 实际读取的 short 样本数量。如果录音停止，可能小于 buffer.length
     */
    @Override
    public int read(short[] buffer) {
        if (buffer == null || buffer.length == 0) {
            return 0;
        }

        int shortsRead = 0;
        while (isRecording && shortsRead < buffer.length) {
            try {
                // poll 方法会等待指定的时间。如果队列中有数据，立即返回；否则等待超时。
                // 这比 take() 方法更好，因为它不会无限期阻塞，
                // 使得循环可以周期性地检查 isRecording 状态。
                Short sample = audioBuffer.poll(100, TimeUnit.MILLISECONDS);

                if (sample != null) {
                    buffer[shortsRead] = sample;
                    shortsRead++;
                }
                // 如果 sample 为 null，说明超时了，循环会继续，并再次检查 isRecording 标志。

            } catch (InterruptedException e) {
                // 消费线程被中断，应该停止读取并退出
                Thread.currentThread().interrupt(); // 保持中断状态
                Log.w(TAG, "Read thread interrupted.");
                break; // 跳出循环
            }
        }

        // 如果不是因为录音停止而退出循环（例如，读取满了buffer），则返回完整的长度
        // 如果是因为录音停止而退出，则返回实际读取到的数量
        return shortsRead;
    }
}